package cn.com.xy.sms.sdk.number;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONObject;

import android.database.Cursor;
import cn.com.xy.sms.sdk.Iservice.XyCallBack;
import cn.com.xy.sms.sdk.constant.Constant;
import cn.com.xy.sms.sdk.db.entity.NumberInfo;
import cn.com.xy.sms.sdk.db.entity.NumberInfoManager;
import cn.com.xy.sms.sdk.db.entity.SysParamEntityManager;
import cn.com.xy.sms.sdk.dex.DexUtil;
import cn.com.xy.sms.sdk.log.LogManager;
import cn.com.xy.sms.sdk.net.NetUtil;
import cn.com.xy.sms.sdk.service.number.ParseNumberService;
import cn.com.xy.sms.sdk.util.CallBackState;
/*SDK-637 zhaojiangwei 20160929 start*/
import cn.com.xy.sms.sdk.util.JsonUtil;
import cn.com.xy.sms.sdk.util.RunnableManager;
import cn.com.xy.sms.sdk.util.StringUtils;
import cn.com.xy.sms.sdk.util.ThreadPoolManager;
import cn.com.xy.sms.sdk.util.XyUtil;
/*SDK-637 zhaojiangwei 20160929 end*/
/* SDK-428 huangzhiqiang 20160615 start */
public class ParseNumberUtil {
    public static final String RUN_IN_THREAD_POOL = "RunInThreadPool";
    public static final String QUERY_DB_RUN_IN_CURRENT_THREAD = "QueryDBRunInCurrentThread";
    private static final Set<String> QUERY_NUM_QUEUE = new HashSet<String>();

    /**
     * 根据号码获取号码数据
     * 
     * @param num 号码
     * @param extend 扩展参数:
     *                  "runInThreadPool":false(可在当前线程执行数据库查询及联网查询操作， 否则默认在新线程执行这些操作)
     *                  "lat":纬度(double类型)(如果传递了该扩展参数，固话自动加区号将使用该纬度，必须和经度一起传递)
     *                  "lng":经度(double类型)(如果传递了该扩展参数，固话自动加区号将使用该经度，必须和纬度一起传递)
     * @param isScrolling 滚动条是否正在滚动(true:是 false:否)，滚动条滚动时不进行数据库查询及联网查询操作
     * @param callBack 
     *            index0(状态，一般只需关注CURRENT_THREAD_HAS_DATA、HAS_DATA这两个状态):
     *                  CallBackState.CURRENT_THREAD_HAS_DATA:当前线程，成功获取号码数据
     *                  CallBackState.CURRENT_THREAD_NO_DATA:当前线程，暂时没有号码数据
     *                  CallBackState.CURRENT_THREAD_NEED_QUERY:当前线程，需要查询数据
     *                  CallBackState.CURRENT_THREAD_SCROLLING:当前线程，滚动条正在滚动，不查询数据库及网络数据
     *                  CallBackState.HAS_DATA:当前线程或新线程，成功获取号码数据
     *                  CallBackState.NO_DATA:当前线程或新线程，暂无号码数据
     *                  CallBackState.ERROR:当前线程或新线程，存在异常，获取号码数据失败
     *            index1:num
     *            index2:
     *                  (index0 == CallBackState.CURRENT_THREAD_HAS_DATA 
     *                  || index0 == CallBackState.HAS_DATA):JSONObject类型 号码数据,例:
     *                              {"pubnum":"10086","numtype":1,"name":"中国移动","area":"广东",
     *                               "version":"201606061325","logo":"af3647264b2c71fd6c03cf9f5e708bdb.png"}
     *                  (index0 != CallBackState.CURRENT_THREAD_HAS_DATA 
     *                  && index0 != CallBackState.HAS_DATA):String类型 提示信息
     * @return
     */
    public static void queryByNum(final String num, final Map<String, String> extend, boolean isScrolling,
            final XyCallBack callBack) {
        /* IMOO-14 likuangkuang 20161018 start */
        // avoid executebypool() executing before initFinish
        if (!InitEmbedNumber.isInitFinish()) {
            XyUtil.doXycallBackResult(callBack, CallBackState.ERROR, num, "init embed number running");
            return;
        }
        /* IMOO-14 likuangkuang 20161018 start */
        
        try {
            // extend内存在经纬度信息时删除旧的经纬度信息
            removeOldLoactionInfoIfNeed(extend);

            // 从缓存中获取号码数据
            final JSONObject data = getNumberCache(num);
            if (data != null) {
                XyUtil.doXycallBackResult(callBack, CallBackState.CURRENT_THREAD_HAS_DATA, num, data);
                return;
            }

            // 滚动条正在滚动，不查询数据库数据及网络数据
            if (isScrolling) {
                XyUtil.doXycallBackResult(callBack, CallBackState.CURRENT_THREAD_SCROLLING, num, "scrolling");
                return;
            }

            // 无效号码，直接返回
            if (ParseNumberCache.isInvalidNum(num)) {
                XyUtil.doXycallBackResult(callBack, CallBackState.CURRENT_THREAD_NO_DATA, num, "no data");
                return;
            }

            // 加入查询队列失败代表号码正在查询队列内，直接返回
            if (!addNumToQueryQueue(num)) {
                XyUtil.doXycallBackResult(callBack, CallBackState.CURRENT_THREAD_NEED_QUERY, num, "in query queue");
                return;
            }

            final boolean queryDBRunInCurrentThread = XyUtil.getBoolean(extend, QUERY_DB_RUN_IN_CURRENT_THREAD, false);
            // 设置了在当前线程执行数据库查询数据
            if (queryDBRunInCurrentThread) {
                Object[] queryResult = queryNumberInfoByDB(num);
                if (queryResult != null) {
                    XyUtil.doXycallBackResult(callBack, queryResult);
                    return;
                }
            }

            ThreadPoolManager.NUMBER_QUERY.execute(new Runnable() {

                @Override
                public void run() {
                    if (!queryDBRunInCurrentThread) {
                        Object[] queryResult = queryNumberInfoByDB(num);
                        if (queryResult != null) {
                            XyUtil.doXycallBackResult(callBack, queryResult);
                            return;
                        }
                    }

                    // 没有联网，无法进行网络接口查询，添加到无效号码缓存
                    if (!NetUtil.checkAccessNetWork(2)) {
                        addInvalidDataCache(num);
                        // 记录因断网无法联网查询的号码
                        NumberInfoManager.insertOrUpdate(num, null, "", System.currentTimeMillis());
                        XyUtil.doXycallBackResult(callBack, CallBackState.ERROR, num, "no network");
                        return;
                    }

                    // 联网接口获取号码数据
                    queryNumberInfoByNet(num, extend, callBack);
                }

            });
            // 先返回“需要查询”状态
            XyUtil.doXycallBackResult(callBack, CallBackState.CURRENT_THREAD_NEED_QUERY, num, "need query");
        } catch (Throwable e) {
            removeNumFromQueryQueue(num);
            XyUtil.doXycallBackResult(callBack, CallBackState.ERROR, num, e.getMessage());
            LogManager.e(Constant.TAG, "ParseNumberUtil queryByNum error:", e);
        } finally {
            executeRunnableByPool();
        }

    }
    /*SDK-637 zhaojiangwei 20160929 start*/
    public static JSONObject queryByNum(String num, Map<String, String> extend) {
        JSONObject jsonObject = null;
        /* IMOO-14 likuangkuang 20161018 start */
        // avoid executebypool() executing before initFinish
        if (!InitEmbedNumber.isInitFinish()) {
            // 正在初始化内嵌数据
            return null;
        }
        /* IMOO-14 likuangkuang 20161018 end */
        
        try {
            if (ParseNumberCache.isInvalidNum(num)) {
                // 无效号码
                return null;
            }
            jsonObject = getNumberCache(num);
            if (jsonObject != null) {
                // 有缓存数据,直接返回.
                return jsonObject;
            }
            if (!addNumToQueryQueue(num)) {
                // 加入查询队列失败代表号码正在查询队列内，直接返回
                return null;
            }
            Object[] queryResult = queryNumberInfoByDB(num);
            if (queryResult != null) {
                // 数据库查询.
                int code = (Integer) queryResult[0];
                if (code == CallBackState.CURRENT_THREAD_HAS_DATA && queryResult.length > 2) {
                    jsonObject = (JSONObject) queryResult[2];
                }
                return jsonObject;
            }
            removeNumFromQueryQueue(num);
        }
        catch (Throwable e) {
            removeNumFromQueryQueue(num);
            LogManager.e(Constant.TAG, "ParseNumberUtil queryByNum error:", e);
        }
        finally {
            executeRunnableByPool();
        }
        return jsonObject;
    }

    public static JSONObject queryByNet(String num, Map<String, String> extend) {
        try {
            if (!InitEmbedNumber.isInitFinish()) {
                // 正在初始化内嵌数据
                return null;
            }
            if (ParseNumberCache.isInvalidNum(num)) {
                // 无效号码
                return null;
            }
            JSONObject cacheJsonObject = getNumberCache(num);
            if (cacheJsonObject != null) {
                // 有缓存数据,直接返回.
                return cacheJsonObject;
            }
            if (!addNumToQueryQueue(num)) {
                // 加入查询队列失败代表号码正在查询队列内，直接返回
                return null;
            }
            // 没有联网，无法进行网络接口查询，添加到无效号码缓存
            if (!NetUtil.checkAccessNetWork(2)) {
                addInvalidDataCache(num);
                // 记录因断网无法联网查询的号码
                NumberInfoManager.insertOrUpdate(num, null, "", System.currentTimeMillis());
                return null;
            }
            final JSONObject jsonObject = new JSONObject();
            // 联网接口获取号码数据
            queryNumberInfoByNet(num, extend, new XyCallBack() {

                @Override
                public void execute(Object... queryResult) {
                    try {
                        if (queryResult == null || queryResult.length < 2) {
                            return;
                        }
                        // 数据库查询.
                        int code = (Integer) queryResult[0];
                        if (code == CallBackState.HAS_DATA) {
                            JsonUtil.JSONCombine(jsonObject, (JSONObject) queryResult[2]);
                        }
                    }
                    catch (Throwable e) {
                        LogManager.e(Constant.TAG,
                                "ParseNumberUtil queryNumberInfoByNet(String num, Map<String, String> extend) execute error:",
                                e);
                    }
                }
            });
            return jsonObject.length() == 0 ? null : jsonObject;
        }
        catch (Throwable e) {
            removeNumFromQueryQueue(num);
            LogManager.e(Constant.TAG,
                    "ParseNumberUtil queryNumberInfoByNet(String num, Map<String, String> extend) error:", e);
        }
        return null;
    }
    
    /*IMOO-20 zhaojiangwei 20161221 start */
    public static Cursor queryAllT9Numbers(){
        return NumberInfoManager.queryAllT9Numbers();
    }
    /*IMOO-20 zhaojiangwei 20161221 end */
    
    /*SDK-637 zhaojiangwei 20160929 end*/
    private static void removeOldLoactionInfoIfNeed(Map<String, String> extend) {
        if (!ParseNumberService.extendHasLatLngInfo(extend)) {
            return;
        }
        // 清空经纬度缓存及数据库记录
        XyUtil.removeLoactionInfo();
        XyUtil.setLoaction(null);
        // 清空旧区号
        XyUtil.removeAreaCodeInfo();
    }
    /*SDK-637 zhaojiangwei 20160929 start*/
    public static JSONObject getNumberCache(String num) {
    /*SDK-637 zhaojiangwei 20160929 end*/
        JSONObject data = ParseNumberCache.getCache(num);
        if (data == null) {
            String areaCodeFixedPhone = addAreaCodeToFixedPhone(num);
            // 没有区号的固话号码带上区号再查询一次缓存
            if (!StringUtils.isNull(areaCodeFixedPhone)) {
                JSONObject fixedPhoneData = ParseNumberCache.getCache(areaCodeFixedPhone);
                // 加上区号后能查到数据并且数据有areaCode字段时才返回，因为areaCode字段是必须的，用于为通话记录号码加上区号
                if (fixedPhoneData != null && !StringUtils.isNull(fixedPhoneData.optString(NumberInfo.AREA_CODE_KEY))) {
                    data = fixedPhoneData;
                }
            }
        }
        return data;
    }

    /**
     * 如果号码是没有区号的固话，返回添加区号后的固话号码
     * 
     * @param num
     * @return
     */
    private static String addAreaCodeToFixedPhone(String num) {
        if (!DexUtil.isNoAreaCodeFixedPhone(num)) {
            return null;
        }
        String areaCode = SysParamEntityManager.getStringParam(Constant.getContext(), Constant.AREA_CODE);
        if (!StringUtils.isNull(areaCode)) {
            return areaCode + num;
        }
        return null;
    }

    private static Object[] queryNumberInfoByDB(String num) {
        JSONObject dbData = NumberInfoManager.queryNumberInfoByDB(num);
        if (dbData == null) {
            String areaCodeFixedPhone = addAreaCodeToFixedPhone(num);
            // 没有区号的固话号码带上区号再查询一次数据库
            if (!StringUtils.isNull(areaCodeFixedPhone)) {
                dbData = NumberInfoManager.queryNumberInfoByDB(areaCodeFixedPhone);
                if (dbData != null) {
                    removeNumFromQueryQueue(num);
                    num = areaCodeFixedPhone;
                }
            }
        }

        if (dbData != null) {
            NumberInfoManager.updateLastQueryTime(num, System.currentTimeMillis());
            if (NumberInfoManager.isEffectiveData(dbData)) {
                // 更新缓存数据
                addEffectiveCache(num, dbData);
                return new Object[] { CallBackState.CURRENT_THREAD_HAS_DATA, num, dbData };
            } else {
                addInvalidDataCache(num);
                return new Object[] { CallBackState.CURRENT_THREAD_NO_DATA, num, "no data" };
            }
        }
        // 数据库没有对应号码数据
        return null;
    }

    /**
     * 联网接口获取号码数据
     * 
     * @param num 号码
     * @param extend 扩展参数:
     *                  "runInThreadPool":false(可在当前线程执行数据库查询及联网查询操作， 否则默认在新线程执行这些操作)
     *                  "lat":纬度(double类型)(如果传递了该扩展参数，固话自动加区号将使用该纬度，必须和经度一起传递)
     *                  "lng":经度(double类型)(如果传递了该扩展参数，固话自动加区号将使用该经度，必须和纬度一起传递)
     * @param callBack 
     *             index0(状态): 
     *                  CallBackState.HAS_DATA:当前线程或新线程，成功获取号码数据
     *                  CallBackState.NO_DATA:当前线程或新线程，暂无号码数据
     *                  CallBackState.ERROR:当前线程或新线程，存在异常，获取号码数据失败
     *             index1:num
     *             index2:
     *                  (index0 == CallBackState.HAS_DATA):JSONObject类型号码数据,例:
     *                      {"pubnum":"10086","numtype":1,"name":"中国移动","area":"广东","version":"201606061325",
     *                       "logo":"af3647264b2c71fd6c03cf9f5e708bdb.png"}
     *                  (index0 == CallBackState.NO_DATA):"no data"
     *                  (index0 == CallBackState.ERROR):String类型 异常信息
     */
    private static void queryNumberInfoByNet(final String num, final Map<String, String> extend,
            final XyCallBack callBack) {
        try {
            Map<String, String> queryNumberMap = new HashMap<String, String>();
            queryNumberMap.put(num, "");
            ParseNumberService.parseByService(queryNumberMap, extend, new XyCallBack() {

                @Override
                public void execute(Object... obj) {
                    try {
                        int callBackState = (Integer) obj[0];
                        Object result = obj[2];
                        if (callBackState != CallBackState.HAS_DATA) {
                            ParseNumberCache.addInvalidNum(num);
                            XyUtil.doXycallBackResult(callBack, callBackState, num, result);
                            return;
                        }

                        // 有效的号码数据并且入库成功，回调号码识别数据
                        JSONObject parseResultJson = ((JSONArray) result).getJSONObject(0);
                        XyUtil.doXycallBackResult(callBack, CallBackState.HAS_DATA, num, parseResultJson);
                    } catch (Throwable e) {
                        LogManager.e(Constant.TAG,
                                "ParseNumberUtil queryNumberInfoByNet execute error:", e);
                        XyUtil.doXycallBackResult(callBack, CallBackState.ERROR, num, e.getMessage());
                    } finally {
                        removeNumFromQueryQueue(num);
                    }
                }
            });
        } catch (Throwable e) {
            removeNumFromQueryQueue(num);
            XyUtil.doXycallBackResult(callBack, CallBackState.ERROR, num, e.getMessage());
            LogManager.e(Constant.TAG, "ParseNumberUtil queryNumberInfoByNet error:", e);
        }
    }

    private static void executeRunnableByPool() {
        RunnableManager.executeRunnableByPool(ThreadPoolManager.REPARSE_NO_NETWORK_NUM_DATA,
                RunnableManager.REPARSE_NO_NETWORK_NUM_INFO);
        RunnableManager.executeRunnableByPool(ThreadPoolManager.REPARSE_INVALID_NUM_DATA,
                RunnableManager.REPARSE_INVALID_NUM_INFO);
        RunnableManager.executeRunnableByPool(ThreadPoolManager.REPARSE_INVALID_NUM_DATA,
                RunnableManager.REPARSE_EXPIRED_NUM_INFO);
        RunnableManager.executeRunnableByPool(ThreadPoolManager.INIT, RunnableManager.UPLOAD_USER_TAG);
        RunnableManager.executeRunnableByPool(ThreadPoolManager.LOACTION, RunnableManager.LOCATION);
		/* IMOO-9 likuangkuang 20161014 start */
		RunnableManager.executeRunnableByPool(ThreadPoolManager.INIT, RunnableManager.OTA_EMBED_NUMBER);
		/* IMOO-9 likuangkuang 20161014 end */
    }

    private static void addEffectiveCache(final String num, JSONObject dbData) {
        ParseNumberCache.addCache(num, dbData);
        ParseNumberCache.removeInvalidNum(num);
        removeNumFromQueryQueue(num);
    }

    private static void addInvalidDataCache(final String num) {
        ParseNumberCache.addInvalidNum(num);
        ParseNumberCache.removeCache(num);
        removeNumFromQueryQueue(num);
    }

    private static boolean addNumToQueryQueue(String num) {
        synchronized (QUERY_NUM_QUEUE) {
            if (!QUERY_NUM_QUEUE.contains(num)) {
                QUERY_NUM_QUEUE.add(num);
                return true;
            }
            return false;
        }
    }

    private static void removeNumFromQueryQueue(String num) {
        synchronized (QUERY_NUM_QUEUE) {
            QUERY_NUM_QUEUE.remove(num);
        }
    }
}
/* SDK-428 huangzhiqiang 20160615 end */